<script setup lang="ts">
import axios from 'axios'
import { computed, ref } from 'vue'

type Item = {
  id: number
  name: string
  number: number
}

const itemHeight = ref(40) // 每一条（项）的高度，比如 40 像素
const count = ref(10) // 一屏展示几条数据
const startIndex = ref(0) // 开始位置的索引
const endIndex = ref(10) // 结束位置的索引
const topVal = ref(0) // 父元素滚动位置

// 所有数据
const allData = ref<Item[]>([])
const showListData = computed(() => allData.value.slice(startIndex.value, endIndex.value))
// 获取数据
const getData = async () => {
  const res = await axios({
    method: 'get',
    url: 'http://localhost:3000/large-data'
  })
  allData.value = res.data.data
}

const viewport = ref<HTMLDivElement>()
// 滚动事件
const onScroll = () => {
  const top = viewport.value?.scrollTop!

  startIndex.value = Math.floor(top / itemHeight.value)
  endIndex.value = startIndex.value + count.value

  topVal.value = top
}
</script>

<template>
  <button @click="getData">获取十万条数据</button>
  <h2>手写虚拟列表-原理</h2>
  <!-- 
    虚拟列表容器：类似“视口”，视口的高度取决于一次展示几条数据
    比如视口只能看到10条数据，一条40像素，10条400像素
    故，视口的高度为400像素，注意要开定位和滚动条 
  -->
  <div
    @scroll="onScroll"
    class="viewport"
    ref="viewport"
    :style="{ height: itemHeight * count + 'px' }"
  >
    <!-- 占位 dom 元素，其高度为所有的数据的总高度 -->
    <div class="placeholder" :style="{ height: allData.length * itemHeight + 'px' }"></div>
    <!-- 内容区，展示10条数据，注意其定位的top值是变化的 -->
    <div class="list" :style="{ top: topVal + 'px' }">
      <!-- 每一条（项）数据 -->
      <div v-for="item in showListData" :key="item.id" class="item">
        {{ item.name }}
      </div>
    </div>
  </div>
</template>

<style scoped lang="scss">
// 虚拟列表容器盒子
.viewport {
  box-sizing: border-box;
  width: 240px;
  border: solid 1px #000000;
  // 开启滚动条
  overflow-y: auto;
  // 开启相对定位
  position: relative;
  .list {
    width: 100%;
    height: auto;
    // 搭配使用绝对定位
    position: absolute;
    top: 0;
    left: 0;
    .item {
      box-sizing: border-box;
      width: 100%;
      height: 40px;
      line-height: 40px;
      text-align: center;
      // 隔行变色
      &:nth-child(even) {
        background: #c7edcc;
      }
      &:nth-child(odd) {
        background: pink;
      }
    }
  }
}
</style>
